import fs, { cp } from "fs";
import path from "path";
import { StructValueType as SVT } from "k-coder";

/**
 * 协议代码构建
 */
export class MsgBuilder {

    /** 消息id字典 */
    private msgIdDic: { [msgId: string]: boolean } = {};
    /** 消息 */
    private msgDefs: IMsgDef[] = [];

    /** 协议定义 */
    private protos: {
        /** 文件名 */
        fileName: string,
        /** 通用结构定义 */
        comDefs: IComStructDef[],
        /** 协议消息定义 */
        msgDefs: IMsgDef[]
    }[] = [];

    constructor(
        /** 协议目录 */
        readonly inputDir: string,
        /** 输出目录 */
        readonly outputDir: string,
        /** 是否输出路由配置 */
        readonly outMsgCfg: boolean,
        /** 输出协议文件名 */
        readonly msgFileName = "Msg",
        /** 输出协议结构文件名 */
        readonly structFileName = "MsgStruct",
        /** 输出协议路由配置文件名 */
        readonly cfgFileName = "MsgCfg",
        /** 输出协议消息id名 */
        readonly msgIdName = "MsgId",
        /** 输出协议消息名 */
        readonly msgName = "Msg",
        /** 输出协议消息Vo名 */
        readonly msgVoName = "MsgVo"
    ) {

    }

    /** 构建协议  */
    build() {
        this.loadAllProto();
        this.sortProto();
        this.buildMsgCode();
        this.buildStructCode();
        this.buildMsgCfgCode();
    }

    /** 读取所有协议 */
    private loadAllProto() {
        //遍历协议目录及其子目录读取协议
        (function eachDir(dir: string, fn: (filePath: string, fileName: string) => void) {
            fs.readdirSync(dir).forEach(fileName => {
                let filePath = path.resolve(dir, fileName);
                if (fs.statSync(path.resolve(dir, fileName)).isDirectory()) {
                    //递归遍历子目录
                    eachDir(filePath, fn);
                } else {
                    fn(filePath, fileName);
                }
            })
        })(this.inputDir, (filePath, fileName) => {
            let match = fileName.match(/(.*)\.kmsg$/);
            if (match) {
                //检查命名是否规范
                console.log(filePath);
                assert(!match[1].match(/[^_0-9a-zA-Z]/), `[${fileName}.kmsg]命名不规范`);
                let file = fs.readFileSync(filePath, "utf-8");
                this.loadProto(file, match[1]);
            }
        });
    }

    /** 读取单个协议 */
    private loadProto(file: string, fileName: string) {

        let comDefs: IComStructDef[] = [];
        let msgDefs: IMsgDef[] = [];
        this.protos.push({ fileName, comDefs, msgDefs });

        //忽略#号后内容
        file = file.replace(/#.*/g, "");
        let def: IStructDef;
        let comDef: IComStructDef;
        let msgDef: IMsgDef;
        let note: string[] = [];
        let nameDic: any = {};
        let fieldIdx = 0;

        //遍历每行读取定义
        file.split(/\r?\n/).forEach((ln, lnIdx) => {
            if (!ln) return;

            let match = ln.match(/^\s*\/\/(.*)/);
            if (match) {
                //匹配到注释
                note.push(match[1]);
                return;
            }
            if (!comDef && !msgDef) {
                //===匹配定义
                match = ln.match(/^\s*(com|[0-9]+(?::[_0-9a-zA-Z]+\.[_0-9a-zA-Z]+)?) +([_0-9a-zA-Z]+) *({|;)/)
                if (match) {
                    //匹配到定义
                    let name = match[2];
                    assert(!nameDic[name], errMsg("vo名重复"));
                    nameDic[name] = true;
                    if (match[1] == "com") {
                        assert(match[3] != ";", errMsg("通用结构定义不能为空"));
                        //通用结构定义
                        fieldIdx = 0;
                        comDefs.push(def = comDef = {
                            fileName,
                            name,
                            note,
                            fields: [],
                            struct: "[]"
                        });
                    } else {
                        //协议消息结构定义
                        let [msgId, router] = match[1].split(":");
                        assert(!this.msgIdDic[msgId], errMsg("协议号重复"));
                        msgDef = {
                            msgId: Number(msgId),
                            msgName: name,
                            fileName,
                            router: router ? router.split(".").length == 3 ? router : `${router}.${name}` : "",
                            note
                        }
                        this.msgIdDic[msgId] = true;
                        this.msgDefs.push(msgDef);
                        msgDefs.push(msgDef);
                        if (match[3] == ";") {
                            //无结构纯定义
                            msgDef = null;
                        }
                    }
                } else {
                    checkOther(ln);
                }
            } else {
                //===读取定义

                /** 匹配字段 */
                function matchField(fields: IFieldDef[]) {
                    match = ln.match(fieldReg);
                    if (match) {
                        //匹配到字段
                        fields.push(parseField(match));
                        return true;
                    }
                    return false;
                }

                /** 匹配结束 */
                function matchEnd() {
                    if (ln.match(/^\s*\}/)) {
                        return true;
                    } else {
                        checkOther(ln);
                    }
                    return false;
                }

                if (comDef) {
                    //===读取通用结构定义
                    if (!matchField(comDef.fields)) {
                        if (matchEnd()) {
                            comDef.struct = buildFieldsStruct(comDef.fields);
                            assert(comDef.struct, "通用结构定义不能为空");
                            def = comDef = null;
                        }
                    }
                } else if (msgDef) {
                    //===读取协议结构定义
                    if (!def) {
                        //匹配tos/toc结构
                        match = ln.match(/^\s*(tos|toc) *{/);
                        if (match) {
                            fieldIdx = 0;
                            def = {
                                note,
                                name: `${match[1]}_${msgDef.msgName}`,
                                fields: [],
                                struct: ""
                            }
                            if (match[1] == "tos") {
                                msgDef.tosStruct = def;
                            } else {
                                msgDef.tocStruct = def;
                            }
                        } else if (matchEnd()) {
                            msgDef = null;
                        }
                    } else {
                        //===读取tos/toc结构定义
                        if (!matchField(def.fields)) {
                            if (matchEnd()) {
                                def.struct = buildFieldsStruct(def.fields);
                                if (!def.struct) {
                                    //没有结构
                                    def.name = "undefined";
                                }
                                def = null;
                            }
                        }
                    }
                }

            }
            note = [];

            function errMsg(msg: string) {
                return `[${fileName}.kmsg]:${lnIdx + 1} >>> ${ln} >>> ${msg}`
            }

            function checkOther(ln: string) {
                assert(!/\S/.test(ln), errMsg("错误定义"))
            }

            /** 解析字段 */
            function parseField(match: RegExpMatchArray): IFieldDef {
                let name = match[1];
                /** 是否使用通用结构 */
                let isCom = match[2] == "@";
                let type = match[3];
                let svt: SVT = (SVT as any)[type];
                /** 特殊浮点型参数 */
                let floatParam = match[4] ? parseInt(match[4]) : 0;
                /** 数组维度 */
                let arrStr = match[5] || "";
                let arrVec = Math.floor(arrStr.length / 2);

                let comSt = "";
                let tsType: string;
                let struct: any[] = [`"${name}"`];

                switch (svt) {
                    case SVT.bool:
                        tsType = "boolean";
                        struct.push(svt);
                        break;
                    case SVT.uint:
                    case SVT.int:
                    case SVT.long:
                    case SVT.ulong:
                    case SVT.floatUint:
                    case SVT.floatInt:
                    case SVT.doubleUlong:
                    case SVT.doubleLong:
                    case SVT.doubleStr:
                        tsType = "number"
                        struct.push(svt);
                        break;
                    case SVT.str:
                        tsType = "string"
                        struct.push(svt);
                        break;
                    case SVT.obj:
                        tsType = "any"
                        struct.push(svt);
                        break;
                    case SVT.any:
                        tsType = "any[]"
                        struct.push(svt);
                        break;
                    default:
                        if (isCom) {
                            //通用结构
                            tsType = type
                            type = type.includes(".") ? type : `${msgDef ? msgDef.fileName : comDef.fileName}.${type}`;
                            if (comDef) {
                                comDef.deps ||= [];
                                comDef.deps.push(fieldIdx, `"${type}"`);
                            } else {
                                comSt = `_["${type}"]`;
                            }
                            struct.push(SVT.struct);
                        } else {
                            assert(false, errMsg("字段解析失败"));
                        }
                        break;
                }
                //数组维度
                tsType += arrStr;
                struct.push(arrVec);
                //特殊参数
                floatParam && struct.push(floatParam);
                comSt && struct.push(comSt);
                !arrVec && struct.length == 3 && struct.pop();
                ++fieldIdx;

                return {
                    lnIdx,
                    note,
                    name,
                    type: tsType,
                    struct: `[${struct.join(", ")}]`,
                }
            }
        });

        /** 构建字段结构 */
        function buildFieldsStruct(fields: IFieldDef[]) {
            if (fields.length) {
                let structs: any[] = [];
                fields.forEach(field => structs.push(field.struct));
                return `[${structs.join(", ")}]`;
            } else {
                return "";
            }
        }
    }

    /** 排序 */
    private sortProto() {
        this.msgDefs.sort((a, b) => a.msgId - b.msgId);
        this.protos.sort((a, b) => a.fileName > b.fileName ? 1 : a.fileName < b.fileName ? - 1 : 0);
    }

    /** 构建协议代码 */
    private buildMsgCode() {
        let code = "//工具生成，请不要手动修改...\n";

        //写入kcoder初始化
        code += `let w: any, r: any, StructCoder: any;\n`;
        code += `/** 传入k-coder来进行初始化 */\n`;
        code += `export function initMsg(kcoder: any) {\n`;
        code += `    w = new kcoder.BinaryWriter();\n`;
        code += `    r = new kcoder.BinaryReader();\n`;
        code += `    StructCoder = kcoder.StructCoder;\n`;
        code += `}\n\n`;

        //写入编解码方法
        code += `const tosCoderDic: any = {}, tocCoderDic: any = {};\n`;
        code += `/** 协议消息编解码 */\n`;
        code += `export const MsgCoder = {\n\n`;

        code += `    /** 加载单个协议消息结构 */\n`;
        code += `    load(msgId: any, struct: any) {\n`;
        code += `        let st: any;\n`;
        code += `        tosCoderDic[msgId] = (st = struct[msgId][0]) ? new StructCoder(st) : undefined;\n`;
        code += `        tocCoderDic[msgId] = (st = struct[msgId][1]) ? new StructCoder(st) : undefined;\n`;
        code += `    },\n\n`;

        code += `    /** 加载所有协议消息结构 */\n`;
        code += `    loadAll(struct: any, onCount?: (count: number) => void) {\n`;
        code += `        let c = 0;\n`;
        code += `        for (let msgId in struct) {\n`;
        code += `            MsgCoder.load(msgId, struct);\n`;
        code += `            onCount && onCount(++c);\n`;
        code += `        }\n`;
        code += `    },\n\n`;

        code += `    /** 编码协议消息id */\n`;
        code += `    encodeMsgId(msgId: MsgId) {\n`;
        code += `        w.start().uint = msgId;\n`;
        code += `    },\n\n`;

        code += `    /** 编码tos协议消息 */\n`;
        code += `    encodeTos(msgId: MsgId, data?: any): Uint8Array {\n`;
        code += `        tosCoderDic[msgId]?.encode(data, w);\n`;
        code += `        return w.end();\n`
        code += `    },\n\n`;

        code += `    /** 编码toc协议消息 */\n`;
        code += `    encodeToc(msgId: MsgId, data?: any): Uint8Array {\n`;
        code += `        tocCoderDic[msgId]?.encode(data, w);\n`;
        code += `        return w.end();\n`
        code += `    },\n\n`;

        code += `    /** 解码协议消息id */\n`;
        code += `    decodeMsgId(buf: Uint8Array) {\n`;
        code += `        return r.start(buf).uint;\n`;
        code += `    },\n\n`;

        code += `    /** 解码tos协议消息 */\n`;
        code += `    decodeTos(msgId: MsgId, buf?: Uint8Array) {\n`;
        code += `        return tosCoderDic[msgId]?.decode(buf || r);\n`;
        code += `    },\n\n`;

        code += `    /** 解码tos协议消息 */\n`;
        code += `    decodeToc(msgId: MsgId, buf?: Uint8Array) {\n`;
        code += `        return tocCoderDic[msgId]?.decode(buf || r);\n`;
        code += `    },\n\n`;

        code += `    /** 解码协议消息id后剩下的buf */\n`;
        code += `    getMsgBuf() {\n`;
        code += `        return r.cut(r.len - r.pos);\n`;
        code += `    }\n`;
        code += `}\n\n`;

        //写入协议号
        code += "/** 协议号 */\n";
        code += `export enum ${this.msgIdName} {\n`;
        for (let msg of this.msgDefs) {
            buildNote(msg.note, "    ");
            code += `    ${msg.msgName} = ${msg.msgId},\n`;
        }
        code += "}\n\n";

        //写入协议消息
        code += `/** 发往服务端的协议消息 */\n`;
        code += `export interface ${this.msgName}Tos {\n`;
        for (let msg of this.msgDefs) {
            if (!msg.tosStruct) continue;
            code += `    [${this.msgIdName}.${msg.msgName}]: ${this.msgVoName}.${msg.fileName}.${msg.tosStruct.name}\n`;
        }
        code += `}\n\n`;

        code += `/** 发往客户端的协议消息 */\n`;
        code += `export interface ${this.msgName}Toc {\n`;
        for (let msg of this.msgDefs) {
            if (!msg.tocStruct) continue;
            code += `    [${this.msgIdName}.${msg.msgName}]: ${this.msgVoName}.${msg.fileName}.${msg.tocStruct.name}\n`;
        }
        code += `}\n\n`;

        //写入协议消息Vo
        code += `/** 协议消息Vo */\n`;
        code += `export namespace ${this.msgVoName} {\n\n`;

        for (let proto of this.protos) {
            let defs: IStructDef[] = [];
            for (let com of proto.comDefs) {
                defs.push(com);
            }
            for (let msg of proto.msgDefs) {
                msg.tosStruct && defs.push(msg.tosStruct);
                msg.tocStruct && defs.push(msg.tocStruct);
            }

            code += `    export namespace ${proto.fileName} {\n\n`;
            for (let def of defs) {
                buildNote(def.note, "        ");
                code += `        export interface ${def.name} {\n`;
                def.fields.forEach(field => {
                    buildNote(field.note, "            ");
                    code += `            ${field.name}: ${field.type},\n`;
                });
                code += `        }\n\n`;
            }
            code += `    }\n\n`;
        }

        code += `}`;

        fs.writeFileSync(path.resolve(this.outputDir, `${this.msgFileName}.ts`), code, { encoding: "utf-8", flag: "w+" });

        /** 构建注释 tab：缩进*/
        function buildNote(note: string[], tab: string) {
            if (!note || !note.length) return;
            if (note.length == 1) {
                //单行注释
                code += `${tab}/** ${note} */\n`;
            } else {
                //多行注释
                code += `${tab}/**\n`;
                note.forEach(line => code += `${tab} * ${line}\n`);
                code += `${tab} */\n`;
            }
        }
    }

    buildStructCode() {
        let code = "//工具生成，请不要手动修改...\n";
        //通用结构
        code += `function $(key: string, st: any, ...deps: any[]) {\n`;
        code += `    deps.length && depFns.push(() => {\n`;
        code += `        for (let i = 0; i < deps.length; ++i) {\n`;
        code += `            _[key][deps[i]][3] = _[deps[++i]];\n`;
        code += `        }\n`;
        code += `    });\n`;
        code += `    Object.defineProperty(_, key, { get: () => Object.defineProperty(_, key, { value: st })[key], enumerable: true, configurable: true })[key];\n`;
        code += `}\n`;
        code += `const _: any = {}, depFns: (() => void)[] = [];\n`;
        for (let proto of this.protos) {
            for (let com of proto.comDefs) {
                code += `$("${proto.fileName}.${com.name}", ${com.struct}${com.deps ? `, ${com.deps.join(", ")}` : ""});\n`;
            }
        }
        code += `depFns.forEach(fn => fn());\n\n`;

        code += `export const msgStrcutCount = ${this.msgDefs.length};\n`;
        code += `export default {\n`;
        for (let msg of this.msgDefs) {
            code += `    ${msg.msgId}: [${msg.tosStruct ? msg.tosStruct.struct : ""},${msg.tocStruct ? ` ${msg.tocStruct.struct}` : ""}],\n`;
        }
        code += `}`;

        fs.writeFileSync(path.resolve(this.outputDir, `${this.structFileName}.ts`), code, { encoding: "utf-8", flag: "w+" });
    }

    buildMsgCfgCode() {
        if (!this.outMsgCfg) return;
        let code = "//工具生成，请不要手动修改...\n";
        code += `export default { \n`;
        for (let msg of this.msgDefs) {
            if (!msg.router) continue;
            code += `    ${msg.msgId}: "${msg.router}", \n`;
        }
        code += `}`;

        fs.writeFileSync(path.resolve(this.outputDir, `${this.cfgFileName}.ts`), code, { encoding: "utf-8", flag: "w+" });
    }
}

/** 字段正则 */
const fieldReg = /^\s*([_0-9a-zA-Z]+\??) *: *(?:(@?)([\._0-9a-zA-Z]+)(?:\((\d+)\))*((?:\[\])+)*)+ *;?/;

/** 断言失败退出程序并输出错误信息 */
function assert(cond: any, msg?: string) {
    if (!cond) {
        console.log(msg);
        process.exit();
    }
    return cond;
}

/** 字段定义 */
interface IFieldDef {
    lnIdx: number, note: string[], name: string, type: string, struct: string, dep?: any[]
}

/** 结构定义 */
interface IStructDef {
    note: string[], name: string, fields: IFieldDef[], struct: string
}

/** 通用结构定义 */
interface IComStructDef extends IStructDef {
    fileName: string, deps?: any[]
}

/** 协议消息定义 */
interface IMsgDef {
    /** 注释 */
    note: string[],
    /** 协议号 */
    msgId: number,
    /** 协议名 */
    msgName: string,
    /** 文件名 */
    fileName: string,
    /** 路由 */
    router: string,
    /** 发服务端结构 */
    tosStruct?: IStructDef,
    /** 发客户端结构 */
    tocStruct?: IStructDef
}